<?php

namespace app\common\service;

use app\common\model\Order;
use app\common\model\Recharge;
use app\common\model\SampleOrder;
use app\common\model\User;
use app\common\model\UserAmount;
use app\common\model\Detailed;
use app\Response;
use think\facade\Cache;
use think\facade\Config;
use think\facade\Log;
use think\facade\Db;

class CommonWechatPayService
{
    //统一下单API
    const PreOrderUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
    //订单退款
    const Refund = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
    // 企业付款到零钱
    const Transfer = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers';
    // 开通商户
    const OpenStore = 'https://api.mch.weixin.qq.com/v3/ecommerce/applyments';

     /**  订单微信支付下单
     */
    public static function orderPayData($user_id,$open_id,$price,$pay_no,$type = 1){
        $data = null;
        if(Cache::has("userAmount".$user_id)&&!empty(Cache::get("userAmount".$user_id))&&Cache::get("userAmount".$user_id)>15){
            return false;
        }
        // 生成微信预支付订单
        $time = time();
        $nonceStr = str_random(32);
        $result = self::preOrder($price, $pay_no, 'JSAPI', $open_id,$nonceStr,$user_id,$type);
        $xml_parser = xml_parser_create();
        xml_parse_into_struct($xml_parser, $result, $re);
        Log::info('统一下单返回：'.json_encode($re));
        if(!isset($re[17])){
            return false;
        }
        $prepay_id = $re[17]['value'];
        // 整理签名数据
        $app_id = Config::get("app.wx_xcx_AppID");
        $key = Config::get("app.wx_key");
        $package = "prepay_id=$prepay_id";
        // 签名加密,
        $pay_sign = strtoupper(md5('appId=' . $app_id . '&nonceStr=' . $nonceStr . '&package=' . $package . '&signType=MD5&timeStamp=' . (string)$time .'&key=' . $key));
        $data = [
            'appid' => $app_id,
            'pay_status' => 0,
            'order_no' => $pay_no,
            'nonce_str' => $nonceStr,
            'package' => $package,
            'time_stamp' => $time,
            'pay_sign' => $pay_sign,
        ];
        return $data;
    }

    /**
     * 生成订单微信支付-预支付订单
     * @param $price int 订单金额，单位分
     * @param $order_no  string 商家自定义的订单编号
     * @param string $trade_type string JSAPI 公众号支付 NATIVE 扫码支付 APP APP支付
     * @return mixed
     * @throws SystemInternalError
     */
    private static function preOrder($price, $order_no, $trade_type = 'NATIVE',$openid='',$nonceStr,$user_id,$type)
    {
        if($type == 1){
            $info = Order::dataCount(["o_pro_num"=>$order_no,"uid"=>$user_id,"state"=>0]);
            if(!is_numeric($info)||$info == 0){
                return "";
            }
        }else{
            $where[] = ["pay_info->out_trade_no","=",$order_no];
            $where[] = ["uid","=",$user_id];
            $o_info = Order::dataCount($where);
            if(!is_numeric($o_info)||$o_info == 0){
                return "";
            }
        }

        $time = time();
        $data = [
            'appid' => Config::get("app.wx_xcx_AppID"),
            'openid' => $openid,
            'mch_id' => Config::get("app.wx_mch_id"),
            'nonce_str' => (string)$nonceStr,
            'detail' => '订单['.$order_no.']支付金额(' . $price  . '元)',
            'attach' => json_encode(["type"=>$type]),//type=1 单订单支付
            'device_info' => 'MINI_APP',
            'body' => '宁海团购订单支付',
            'total_fee' => ($price * 100),
            'fee_type' => 'CNY',
            'time_start' => date('YmdHis', $time),
            'time_expire' => date('YmdHis', $time + 600),
            'trade_type' => $trade_type,
            'out_trade_no' => $order_no,
            'notify_url' => HOST.Config::get("app.NotifyUrl"),
        ];
        self::generateSign($data);
        $data = self::convertArrayToXml($data);
        $result = self::post(self::PreOrderUrl, $data);
        if (empty($result)) return Response::wrapError('wechat preorder failed!');

        return $result;
    }


    /** 订单支付回调
    */
    public static function wxOrderPayRollBack($data){
        $pay_no = $data['out_trade_no'];//订单号
        $third_order_no = $data['transaction_id'];//微信支付单号
        $where[] = ['o_pro_num',"=",$pay_no];
        $o_info = Order::dataInfo($where);
        if (empty($pay_no)){
            Log::info('支付单号不存在：' . $pay_no);
        }
        $userAmount = UserAmount::userAmountInfo(["uid"=>$o_info["uid"]]);
        $time = time();
        $pay_info = ["transaction_id"=>$third_order_no,"out_trade_no"=>$pay_no];
        Db::startTrans();
        try {
            if($o_info["type"] == 1){
                Order::editInfo(["id"=>$o_info["id"],"o_pro_num"=>$pay_no],["state"=>1,"paytime"=>$time,"pay_info"=>$pay_info]);
            }elseif($o_info["type"] == 2){
                Order::editInfo(["id"=>$o_info["id"],"o_pro_num"=>$pay_no],["state"=>2,"paytime"=>$time,"pay_info"=>$pay_info,"sendtime"=>time()]);
            }
            UserAmount::userAmountInc(["uid"=>$o_info["uid"]],"accumulated_amount",$o_info['low_price']);
            User::dataInc(["uid"=>$o_info["uid"]],"order_num",1);
            $detailed[] = \detailed($o_info['uid'],$o_info['low_price'],'order_wxpay',$userAmount['balance'],$o_info["id"],"","",$pay_no);
            Detailed::detailedAddAll($detailed);
            Db::commit();
        }catch (\Exception $e){
            Db::rollback();
        }
    }


    public static function wxCartOrderPayRollBack($data){
        $pay_no = $data['out_trade_no'];//订单号
        $third_order_no = $data['transaction_id'];//微信支付单号
        $where[] = ["pay_info->out_trade_no","=",$pay_no];
        $sum = bcdiv($data["total_fee"],100,2);
        $o_info = Order::dataInfo($where);
        $userAmount = UserAmount::userAmountInfo(["uid"=>$o_info["uid"]]);
        $time = time();
        $where[] = ["uid","=",$o_info["uid"]];
        $count = Order::dataCount($where);
        $pay_info = ["transaction_id"=>$third_order_no,"out_trade_no"=>$pay_no];
        Db::startTrans();
        try {
            if($o_info["type"] == 1){
                Order::editInfo($where,["state"=>1,"paytime"=>$time,"pay_info"=>$pay_info]);
            }elseif($o_info["type"] == 2){
                Order::editInfo($where,["state"=>2,"paytime"=>$time,"pay_info"=>$pay_info,"sendtime"=>time()]);
            }
            UserAmount::userAmountInc(["uid"=>$o_info["uid"]],"accumulated_amount",$sum);
            User::dataInc(["uid"=>$o_info["uid"]],"order_num",$count);
            $detailed[] = \detailed($o_info["uid"],$sum,'order_wxpay',$userAmount['balance'],"","","",$pay_no);
            Detailed::detailedAddAll($detailed);
            Db::commit();
        }catch (\Exception $e){
            Db::rollback();
        }
    }

    /********  微信充值区  ********/
    //微信充值余额生成支付信息
    public static function miniPayData($user_id,$openid,$price, $pay_no)
    {
        $data = null;
        if(Cache::has("userAmount".$user_id)&&!empty(Cache::get("userAmount".$user_id))&&Cache::get("userAmount".$user_id)>15){
            return Response::wrapError(0,'系统结算中请稍后再试!');
        }
        Db::transaction(function () use ($openid,$price, $pay_no, &$data) {
            // 生成微信预支付订单
            $time = time();
            $nonceStr = str_random(32);
            $result = self::wxRechargOrder($price, $pay_no, 'JSAPI', $openid,$nonceStr);
            if(empty($result)){
                $data = -1;
            }
            $xml_parser = xml_parser_create();
            xml_parse_into_struct($xml_parser, $result, $re);
            Log::info('统一下单返回：'.json_encode($re));
            if(!isset($re[17])){
                return false;
            }
            $prepay_id = $re[17]['value'];
            // 整理签名数据
            $app_id = Config::get("app.wx_xcx_AppID");
            $key = Config::get("app.wx_key");
            $package = "prepay_id=$prepay_id";
            // 签名加密,
            $pay_sign = strtoupper(md5('appId=' . $app_id . '&nonceStr=' . $nonceStr . '&package=' . $package . '&signType=MD5&timeStamp=' . (string)$time .'&key=' . $key));
            $data = [
                'appid' => $app_id,
                'pay_status' => 0,
                'order_no' => $pay_no,
                'nonce_str' => $nonceStr,
                'package' => $package,
                'time_stamp' => $time,
                'pay_sign' => $pay_sign,
            ];
        });
        return $data;
    }


    /**
     * 生成微信充值预支付订单
     * @param $price int 订单金额，单位分
     * @param $order_no  string 商家自定义的订单编号
     * @param string $trade_type string JSAPI 公众号支付 NATIVE 扫码支付 APP APP支付
     * @return mixed
     * @throws SystemInternalError
     */
    private static function wxRechargOrder($price, $order_no, $trade_type = 'NATIVE',$openid='',$nonceStr)
    {
        $info = Recharge::rechargeCount(["no_order"=>$order_no,"state"=>0]);
        if(!is_numeric($info)||$info == 0){
            return "";
        }
        $time = time();
        $data = [
            'appid' => Config::get("app.wx_xcx_AppID"),
            'openid' => $openid,
            'mch_id' => Config::get("app.wx_mch_id"),
            'nonce_str' => (string)$nonceStr,
            'detail' => '柚米麻豆余额充值(' . $price  . '元)',
            'attach' => urlencode(["type"=>3]),//3充值
            'device_info' => 'MINI_APP',
            'body' => '柚米麻豆商',
            'total_fee' => ($price * 100),
            'fee_type' => 'CNY',
            'time_start' => date('YmdHis', $time),
            'time_expire' => date('YmdHis', $time + 600),
            'trade_type' => $trade_type,
            'out_trade_no' => $order_no,
            'notify_url' => HOST.Config::get("app.NotifyUrl"),
        ];
        self::generateSign($data);
        $data = self::convertArrayToXml($data);
        $result = self::post(self::PreOrderUrl, $data);
        if (empty($result)) return Response::wrapError('wechat preorder failed!');

        return $result;
    }



    //余额充值回调
    public static function wxRechargBalancePayRollBack($data){
        $pay_no = $data['out_trade_no'];//订单号
//        $third_order_no = $data['transaction_id'];//微信支付单号
        if (empty($pay_no))
            Log::info('支付单号不存在：' . $pay_no);
        $recharge = Recharge::rechargeQuery(['no_order'=>$data['out_trade_no']]);//获取本地数据库订单信息
        if(!empty($recharge)) {
            Db::startTrans();
            try {
                $time = time();
                //回调信息记录 状态修改
                $newData = [
                    'state' => 1,
                    'remarks' => '充值成功',
                    'audittime' => time(),
                    'action_data' => json_encode($data),
                ];
                Recharge::rechargeEdit(['no_order' => $data['out_trade_no']], $newData);
                //充值记录增加  余额增加
                RechargeService::rechargeDetailedAdd($recharge,'recharge_wxpay');
                Cache::delete("merchantAmount".$recharge["uid"]);
                Db::commit();
            } catch (\Exception $e) {
                Db::rollback();
            }
        }
    }



    /********  公共方法区域  ********/
    //生成签名
    private static function getSign($params) {
        ksort($params);        //将参数数组按照参数名ASCII码从小到大排序
        foreach ($params as $key => $item) {
            if (!empty($item)) {         //剔除参数值为空的参数
                $newArr[] = $key.'='.$item;     // 整合新的参数数组
            }
        }
        $stringA = implode("&", $newArr);         //使用 & 符号连接参数
        $key = Config::get("app.wx_key");

        $stringSignTemp = $stringA."&key=".$key;        //拼接key
        // key是在商户平台API安全里自己设置的
        $stringSignTemp = MD5($stringSignTemp);       //将字符串进行MD5加密
        $sign = strtoupper($stringSignTemp);      //将所有字符转换为大写
        return $sign;
    }


    /**
     * 参数签名
     * @param $data
     */
    private static function generateSign(&$data)
    {
        ksort($data);//参数按照ASCII有小到大排序
        $stringA = '';
        //生成stringA
        $length = count($data);
        $i = 0;
        foreach ($data as $k => $v) {
            $i++;
            $stringA .= $k . '=' . $v;
            if ($i != $length) {
                $stringA .= '&';
            }
        }

        $stringSignTemp = $stringA . '&key=' . Config::get("app.wx_key");
        $data['sign'] = strtoupper(md5($stringSignTemp));
    }

    /**
     * 将array转变为xml
     * @param $data
     * @return string
     */
    private static function convertArrayToXml($data)
    {
        if (empty($data)) return 'error';
        $xml = '<xml>' . "\n";
        foreach ($data as $k => $v) {
            $xml .= '<' . $k . '>' . $v . '</' . $k . '>' . "\n";
        }
        $xml .= '</xml>';
        return $xml;
    }




    /**
     * 针对微信返回的xml参数，结构化为操作便利的数组
     * @param $xml
     * @return array
     */
    public static function xmlStructureToArray($xml)
    {
        $parser = xml_parser_create();
        xml_parse_into_struct($parser, $xml, $arr);
        $data = [];
        foreach ($arr as $item) {
            if ($item['tag'] != 'XML')
                $data[strtolower($item['tag'])] = $item['value'];
        }
        unset($arr, $xml, $parser);
        return $data;
    }


    /**
     * 解析微信退款内容  AES-256-ECB  PKCS7Padding
     * @param $info
     * @return array
     * @throws SystemInternalError
     */
    public function decodeRefundNotifyInfo($info)
    {
        $data = base64_decode($info);
        $key = strtolower(md5($this->conf['Key']));
        $re = openssl_decrypt($data, 'AES-256-ECB', $key, OPENSSL_NO_PADDING);
        if ($re == false)
            throw new SystemInternalError('info decryption failed!');
        $len = strlen($re);
        $pad = ord($re[$len - 1]);
        if ($pad < 1 || $pad > 32)
            $pad = 0;
        $xml = substr($re, 0, $len - $pad);
        return $this->xmlStructureToArray($xml);
    }

    public function openStore()
    {
        $data = [
            'out_request_no'=>'1asdas',// 随机字符串
            'organization_type'=>'2401',
            'id_doc_type'=>'IDENTIFICATION_TYPE_MAINLAND_IDCARD',
            'id_card_info'=>[
                'id_card_copy'=>'身份证人像面照片,通过图片上传接口预先上传图片生成好的MediaID',
                'id_card_national'=>'身份证国徽面照片,通过图片上传接口预先上传图片生成好的MediaID',
                'id_card_name'=>'经营者姓名，该字段需进行加密处理，加密方法详见敏感信息加密说明',
                'id_card_number'=>'15位数字或17位数字+1位数字|X ，该字段需进行加密处理，加密方法详见敏感信息加密说明',
                'id_card_valid_time'=>'示例值：2026-06-06，长期'
            ],
            'need_account_info'=>true,
            'account_info'=>[
                'bank_account_type'=>'75',//若主体为小微，可填写：75-对私账户。
                'account_bank'=>'详细参见开户银行对照表',
                'account_name'=>'开户名称,',//该字段需进行加密处理，加密方法详见敏感信息加密说明。(提醒：必须在HTTP头中上送Wechatpay-Serial)
                'bank_address_code'=>'',//至少精确到市，详细参见省市区编号对照表。
                'bank_branch_id'=>'',//详细参见开户银行全称（含支行）对照表
                'bank_name'=>'',//详细参见开户银行全称（含支行）对照表
                'account_number'=>'',//该字段需进行加密处理，加密方法详见敏感信息加密说明。(提醒：必须在HTTP头中上送Wechatpay-Serial)
            ],
            'contact_info'=>[// 管理员信息
                'contact_type'=>'65',
                'contact_name'=>'id_card_name',// 与法人姓名一致
                'contact_id_card_number'=>'id_card_number',//与法人身份证一致
                'mobile_phone'=>'',// 手机号码
                'contact_email'=>'该字段需进行加密处理，加密方法详见敏感信息加密说明。(提醒：必须在HTTP头中上送Wechatpay-Serial)'
            ],
            'sales_scene_info'=>[
                'merchant_shortname'=>'',//将在支付完成页向买家展示，需与商家的实际售卖商品相符
            ]
        ];
    }


    /**
     * post方法
     * @param $url
     * @param $data
     * @param null $header
     * @param bool $is_debug
     * @param null $cert_path
     * @param null $pkey_path
     * @param null $ca_path
     * @return mixed
     * @throws SystemErr
     */
    public static function post($url,$data,$header=null,$is_debug=false,$cert_path=null,$pkey_path=null,$ca_path=null)
    {
        $ch = curl_init();
        curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
        if($header)
            curl_setopt($ch,CURLOPT_HTTPHEADER,$header);
        curl_setopt($ch,CURLOPT_POST,true);
        curl_setopt($ch,CURLOPT_POSTFIELDS,$data);
        curl_setopt($ch,CURLOPT_URL,$url);
        curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false);
        curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
        if($cert_path && $pkey_path)
        {
            curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLCERT,$cert_path);
            curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLKEY,$pkey_path);
        }
        if($ca_path) {
            curl_setopt($ch, CURLOPT_CAINFO, 'PEM');
            curl_setopt($ch, CURLOPT_CAPATH, $ca_path);
        }

        $re = curl_exec($ch);
        if($is_debug)
            return $re;
        $code = curl_getinfo($ch,CURLINFO_HTTP_CODE);
        if($code != 200)
            return $code;
        return $re;
    }
}